TrainingPeaks Data

Preliminary tasks

Load Libraries

Load libraries for reading fit data and create maps and plots.


Manual Example (WhiteLiam)

Date: 16 April 2019, cycling

File: fit-test_cycling_2019-04-16_05-29-42.csv.fit

What can we get from TrainingPeaks?

Lots of metadata from the fit file: * file id * device settings * user properties * sport / sport type * etc.

 [1] "file_id"         "device_settings" "user_profile"    "zones_target"   
 [5] "sport"           "session"         "lap"             "record"         
 [9] "event"           "device_info"     "activity"        "file_creator"   
[13] "hrv"             "unknown"        

And more specifics about the activity:

With their corresponding units:

      [,1]                         [,2]     
 [1,] "accumulated_power"          "watts"  
 [2,] "altitude"                   "m"      
 [3,] "cadence"                    "rpm"    
 [4,] "combined_pedal_smoothness"  "percent"
 [5,] "distance"                   "m"      
 [6,] "enhanced_altitude"          "m"      
 [7,] "enhanced_speed"             "m/s"    
 [8,] "fractional_cadence"         "rpm"    
 [9,] "heart_rate"                 "bpm"    
[10,] "left_pedal_smoothness"      "percent"
[11,] "left_right_balance"         ""       
[12,] "left_torque_effectiveness"  "percent"
[13,] "position_lat"               "degrees"
[14,] "position_long"              "degrees"
[15,] "power"                      "watts"  
[16,] "right_pedal_smoothness"     "percent"
[17,] "right_torque_effectiveness" "percent"
[18,] "speed"                      "m/s"    
[19,] "temperature"                "C"      
[20,] "timestamp"                  "s"      

Basic Plots

Heart Rate vs Gradient

The steepest the climb, the hardest the heart has to work.

With the corresponding correlation:


Call:
lm(formula = heart_rate ~ poly(gradient, 2), data = pdata)

Residuals:
   Min     1Q Median     3Q    Max 
-88.10 -12.44  -1.12  11.48  69.91 

Coefficients:
                    Estimate Std. Error t value Pr(>|t|)    
(Intercept)         136.9456     0.2314  591.83   <2e-16 ***
poly(gradient, 2)1 2121.3849    19.6863  107.76   <2e-16 ***
poly(gradient, 2)2  264.0658    19.6863   13.41   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 19.69 on 7235 degrees of freedom
Multiple R-squared:  0.6198,    Adjusted R-squared:  0.6196 
F-statistic:  5896 on 2 and 7235 DF,  p-value: < 2.2e-16

And averages for each gradient step:

               [,1]     [,2]    [,3]     [,4]     [,5]     [,6]     [,7]
gradient   -6.00000  -4.0000  -2.000   0.0000   2.0000   4.0000   6.0000
heart_rate 96.57316 105.1922 115.464 127.3885 140.9658 156.1958 173.0786

Activity Maps

Convert time to minutes and add direction (Outbound vs Return):

Map with HR

The darker the colour, the higher the HR is:

Map with HR and profile

And we can overlap different measurements to the same plot. The darker the colour, the higher the HR and the higher the altitude (for the new line):

Map of a different activity

Function to create map:


TrainingPeaks Analysis

Extract predictors from activities

We want to calculate different predictors based on the training activity: Phase 1 will contain:

  • Number of Activities
  • Volume (Time)
  • Intensity
    • %HRmax
    • Thresholds (Time in T1/T2)
    • Volume x %HRmax
  • Sessions per week
  • Average time
  • Average speed (cycling vs running, etc?)
  • Average HR
  • Heart beats per week/year of training
  • Average accumulated power
  • Data from TrainingPeaks? (data$session)
    • Calories
    • Total ascent
    • max_power
    • average_power / normalized_power
    • total work
    • training_stress_score
    • total_distance
    • intensity_factor
    • avg_HR

Predictors

Read activity data (Only 1 for the moment, but will loop through all activities later):

Initialize predictors df with activity id:

  • Number of Activities
  • Volume (Time)
  • Intensity
    • %HRmax
    • Volume x %HRmax
  • Sessions per week (ATHLETE CALCULATION) Will add a week counter
  • Average HR
  • Average speed (cycling vs running, etc?)
    • Event type = 1 == cycling??
    • sport = 2?
  • Average accumulated power (ATHLETE CALCULATION)
  • Heart beats per week/year of training (ATHLETE CALCULATION)
  • Data from TrainingPeaks? (data$session)
    • Calories
  • Total ascent
  • max_power
  • average_power / normalized_power
  • total work
  • training_stress_score
  • total_distance
  • intensity_factor

Which will give us a table as follows:


Subset 10 athletes

Athlete original data

We will have VO2max for all participants (Name, VO2max, Weight):

Athlete input files

Get all files for the 10 athletes (September 2019):

[1] "SELECTED_FILES/WhiteLiam/09"
[1] "WhiteL1994.2019-09-01-03-14-36-346Z.GarminPush.39879313546.fit"
[1] "SELECTED_FILES/MichelmoreKatie/09"
[1] "katiemichelmore.2019-09-04-00-33-02-272Z.GarminPush.40039260489.fit"
[1] "SELECTED_FILES/WhiteNicholas/09"
[1] "nickwhitey97.2019-09-01-13-48-28-116Z.GarminPush.39904430313.fit"
[1] "SELECTED_FILES/JennerSamuel/09"
[1] "2019-09-01-095353-ELEMNT BOLT 6A50-69-0.fit"
[1] "SELECTED_FILES/LoneraganBryn/09"
[1] "bloneragan.2019-09-01-05-32-27-608Z.GarminPush.39882875786.fit"
[1] "SELECTED_FILES/WillOckenden/09"
[1] "2019-09-03-191932-ELEMNT BOLT 8AFB-140-0.fit"
[1] "SELECTED_FILES/PaolilliDomenic/09"
[1] "DomenicPaolilli.2019-09-04-05-08-40-874Z.GarminPush.40048137906.fit"
[1] "SELECTED_FILES/Veriskirstydeacon/09"
[1] "kirstydeacon.2019-09-01-03-46-50-971Z.GarminPush.39879951239.fit"
[1] "SELECTED_FILES/AlbrechtJasper/09"
[1] "ALBRECHT1.2019-09-01-09-40-41-058Z.GarminPush.39893460390.fit"
[1] "SELECTED_FILES/McKennaDylan/09"
[1] "dylanmckenna98.2019-09-01-05-07-55-531Z.GarminPush.39882056069.fit"

Predictors for all activities

Our goal now is to create a framework that inputs a list of files and returns a table with all predictors. First for all Fit files of athletes and then, calculate totals and averages for each of the athletes.

Test Case (WhiteLiam)

Get all WhiteLiam files

And create a function to calculate all predictors as previously, given a list of Fit files:

calc_predictors <- function (fitFiles) {
  #initialize predictors
  all.predictors <- as.data.frame(matrix(vector(),ncol=25))
  colnames(all.predictors) <- c("activity.id","time.min","hrmax.athlete","hrmax.activity",
                            "hrmax.perc","hrmax.intensity","hr.z5","hr.z4","hr.z3",
                            "hr.z2","hr.z1","week","hr.avg","sport_code","sport_type","speed.avg","cal",
                            "ascent","power.max","power.avg","power.norm","work",
                            "stress.score","total.dist","intensity.factor")
  activity.id = 1
  for (myfile in fitFiles) {
     # print(myfile)
     data <- read.fit(myfile)
     #check if there are NAs in data$record (some cases seen) --> JennerSamuel [5]
     # if t
     predictors <- as.data.frame(matrix(NA,ncol=25))
     colnames(predictors) <- c("activity.id","time.min","hrmax.athlete","hrmax.activity",
                            "hrmax.perc","hrmax.intensity","hr.z5","hr.z4","hr.z3",
                            "hr.z2","hr.z1","week","hr.avg","sport_code","sport_type",
                            "speed.avg","cal",
                            "ascent","power.max","power.avg","power.norm","work",
                            "stress.score","total.dist","intensity.factor")
     activity.time <- round((max(data$record$timestamp)-data$record$timestamp[1])/60,2)
     predictors$time.min <- activity.time
     predictors$activity.id <- activity.id
     #check if there is heart_rate info (not in swimming, surfing and other watersports)
     if(is.null(data$zones_target$max_heart_rate)){
       predictors$hrmax.athlete <- 200
     } else {
       predictors$hrmax.athlete <- data$zones_target$max_heart_rate
     }
     if (any(names(data$record) %in% "heart_rate")){
       predictors$hrmax.activity <- max(data$record$heart_rate
                                        [1:(length(data$record$heart_rate)-3)],na.rm=T)
       predictors$hrmax.perc <- round(predictors$hrmax.activity/predictors$hrmax.athlete*100,2)
       predictors$hrmax.intensity <- predictors$hrmax.perc * predictors$time.min
       hr.zones <- quantile(c(1:predictors$hrmax.athlete),probs=seq(0,1,by=0.1))
       data$record$hr.zones <- findInterval(data$record$heart_rate,hr.zones[6:10])
       hr.zones.table<-round(table(data$record$hr.zones)/sum(table(data$record$hr.zones))*100,1)
       predictors$hr.z5 <- hr.zones.table[6]
       predictors$hr.z4 <- hr.zones.table[5]
       predictors$hr.z3 <- hr.zones.table[4]
       predictors$hr.z2 <- hr.zones.table[3]
       predictors$hr.z1 <- hr.zones.table[2]
       predictors$hr.avg <- data$session$avg_heart_rate
     } else {
       predictors$hrmax.athlete <- NA
       predictors$hrmax.activity <- NA
       predictors$hrmax.perc <- NA
       predictors$hrmax.intensity <- NA
       predictors$hr.z5 <- NA
       predictors$hr.z4 <- NA
       predictors$hr.z3 <- NA
       predictors$hr.z2 <- NA
       predictors$hr.z1 <- NA
       predictors$hr.avg <- NA
     }
     predictors$week <- 1
     predictors$sport_code <- data$session$sport
     predictors$sport_type <- sport_type[which (sport_code == predictors$sport_code)]
     predictors$speed.avg <- round(data$session$avg_speed*3.79,1)
     predictors$cal <- if(is.null(data$session$total_calories)){NA}else{data$session$total_calories}
     predictors$ascent <- if(is.null(data$session$total_ascent)){NA}else{data$session$total_ascent}
     predictors$power.max <- if(is.null(data$session$max_power)){NA}else{data$session$max_power}
     predictors$power.avg <- if(is.null(data$session$avg_power)){NA}else{data$session$avg_power}
     predictors$power.norm <- if(is.null(data$session$normalized_power)){NA}else{data$session$normalized_power}
     predictors$work <- if(is.null(data$session$total_work)){NA}else{data$session$total_work}
     predictors$stress.score <- if(is.null(data$session$training_stress_score)){NA}
                                else {data$session$training_stress_score}
     predictors$total.dist <- data$session$total_distance
     predictors$intensity.factor <-  if(is.null(data$session$intensity_factor)){NA}
                                     else{data$session$intensity_factor}
     all.predictors <- rbind(all.predictors,predictors)
     activity.id <- activity.id+1
     # print(all.predictors)
  }
   return(all.predictors)
}

Example Output

The function created above gives us the following results for the test with WhiteLiam files:


Superpredictors as summary

Superpredictors are averages or totals of each of the activity predictors, as a way to summarize training data for each athlete during the selected period. Here we calculate all of them to compare with vo2max in the final step.

Definition

Define function to: * Initialize variables

  • Calculate variables
# ath <- 'Liam'
get_superpredictors <- function (ath.data,ath.predictors,ath.name) {
  ath.data[ath.data$name == ath.name,]$activities.total <- nrow(ath.predictors)
  ath.data[ath.data$name == ath.name,]$time.total <- sum(ath.predictors$time.min)
  ath.data[ath.data$name == ath.name,]$time.avg <- mean(ath.predictors$time.min,na.rm=T)
  ath.data[ath.data$name == ath.name,]$hrmax.perc.avg <- mean(ath.predictors$hrmax.perc,na.rm=T)
  ath.data[ath.data$name == ath.name,]$hrmax.max <- if (sum(!is.na(ath.predictors$hrmax.activity)) == 0 ){NA}else{max(ath.predictors$hrmax.activity,na.rm=T)}
  ath.data[ath.data$name == ath.name,]$hrmax.intensity.total <- sum(ath.predictors$hrmax.intensity,na.rm=T)
  ath.data[ath.data$name == ath.name,]$hrmax.intensity.avg <- mean(ath.predictors$hrmax.intensity,na.rm=T)
  ath.data[ath.data$name == ath.name,]$hr.z5.avg <- mean(ath.predictors$hr.z5,na.rm=T)
  ath.data[ath.data$name == ath.name,]$hr.z4.avg <- mean(ath.predictors$hr.z4,na.rm=T)
  ath.data[ath.data$name == ath.name,]$hr.z3.avg <- mean(ath.predictors$hr.z3,na.rm=T)
  ath.data[ath.data$name == ath.name,]$hr.z2.avg <- mean(ath.predictors$hr.z2,na.rm=T)
  ath.data[ath.data$name == ath.name,]$hr.z1.avg <- mean(ath.predictors$hr.z1,na.rm=T)
  ath.data[ath.data$name == ath.name,]$hr.avg <- mean(ath.predictors$hr.avg,na.rm=T)
  ath.data[ath.data$name == ath.name,]$cal.total <- sum(ath.predictors$cal,na.rm=T)
  ath.data[ath.data$name == ath.name,]$cal.avg <- mean(ath.predictors$cal,na.rm=T)
  ath.data[ath.data$name == ath.name,]$power.avg <- mean(ath.predictors$power.avg,na.rm=T)
  ath.data[ath.data$name == ath.name,]$work.avg <- mean(ath.predictors$work,na.rm=T)
  ath.data[ath.data$name == ath.name,]$stress.score.avg <- mean(ath.predictors$stress.score,na.rm=T)
  ath.data[ath.data$name == ath.name,]$dist.total <- sum(ath.predictors$total.dist,na.rm=T)
  ath.data[ath.data$name == ath.name,]$dist.avg <- mean(ath.predictors$total.dist,na.rm=T)
  ath.data[ath.data$name == ath.name,]$intensity.factor.avg <- mean(ath.predictors$intensity.factor,na.rm=T)
  ath.data[is.na(ath.data)] <- NA
  return(ath.data)
}

Calculate Superpredictors

Combine the reading of the files with the superpredictor calculation

And the final results for all the athletes:


Make correlation with VO2max

Correlate all superpredictors with VO2max. Each point corresponds to an athlete:

  • Model with all variables
only using the first two of 4 regression coefficients


Call:
lm(formula = md, data = ath.superpreds)

Residuals:
     Min       1Q   Median       3Q      Max 
-0.56419 -0.24564  0.02118  0.28234  0.40622 

Coefficients:
                   Estimate Std. Error t value Pr(>|t|)  
(Intercept)       4.4153795  2.2613275   1.953   0.0987 .
Weight           -0.0052206  0.0382897  -0.136   0.8960  
activities.total -0.0181568  0.0339628  -0.535   0.6121  
time.total        0.0001865  0.0001143   1.631   0.1540  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.4167 on 6 degrees of freedom
Multiple R-squared:  0.4025,    Adjusted R-squared:  0.1037 
F-statistic: 1.347 on 3 and 6 DF,  p-value: 0.3449
LS0tCnRpdGxlOiAiVHJhaW5pbmcgUGVha3MiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jOiB0cnVlCiAgICB0b2NfZGVwdGg6IDMKICAgIHRvY19mbG9hdDoKICAgICAgY29sbGFwc2VkOiBmYWxzZQogICAgICBzbW9vdGhfc2Nyb2xsOiB0cnVlCiAgICB0aGVtZTogY29zbW8KICAgIGRmX3ByaW50OiBwYWdlZAogICAgaGlnaGxpZ2h0OiB0YW5nbwogICAgY29kZV9mb2xkaW5nOiBoaWRlCiAgICAjIGZpZ193aWR0aDogMTIKICAgICMgZmlnX2hlaWdodDogMTIKIyBvdXRwdXQ6CiMgICBlcHVSYXRlOjpCQUtFUjoKIyAgICAgdG9jOiBUUlVFCiMgICAgIG51bWJlcl9zZWN0aW9uczogRkFMU0UKIyAgICAgY29kZV9mb2xkaW5nOiAic2hvdyIKLS0tCgo8c2NyaXB0PgokKGRvY3VtZW50KS5yZWFkeShmdW5jdGlvbigpIHsKICAkaXRlbXMgPSAkKCdkaXYjVE9DIGxpJyk7CiAgJGl0ZW1zLmVhY2goZnVuY3Rpb24oaWR4KSB7CiAgICBudW1fdWwgPSAkKHRoaXMpLnBhcmVudHNVbnRpbCgnI1RPQycpLmxlbmd0aDsKICAgICQodGhpcykuY3NzKHsndGV4dC1pbmRlbnQnOiBudW1fdWwgKiAxMCwgJ3BhZGRpbmctbGVmdCc6IDB9KTsKICB9KTsKCn0pOwo8L3NjcmlwdD4KCioqKgoKIyBUcmFpbmluZ1BlYWtzIERhdGEKCiMjIFByZWxpbWluYXJ5IHRhc2tzCiMjIyBMb2FkIExpYnJhcmllcwoKTG9hZCBsaWJyYXJpZXMgZm9yIHJlYWRpbmcgZml0IGRhdGEgYW5kIGNyZWF0ZSBtYXBzIGFuZCBwbG90cy4KCmBgYHtyIHdhcm5pbmc9RkFMU0UsIHJlc3VsdHM9J2hpZGUnLCBlY2hvPSdGQUxTRSd9CmxpYnJhcnkoZml0KQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkobGVhZmxldCkKbGlicmFyeShkcGx5cikKbGlicmFyeShjZXRjb2xvcikKYGBgCgoqKiogCiMjIE1hbnVhbCBFeGFtcGxlIChXaGl0ZUxpYW0pCgpEYXRlOiAxNiBBcHJpbCAyMDE5LCBjeWNsaW5nCgpGaWxlOiBmaXQtdGVzdF9jeWNsaW5nXzIwMTktMDQtMTZfMDUtMjktNDIuY3N2LmZpdAoKYGBge3J9CmRhdGEgPC0gcmVhZC5maXQoJ2ZpdC10ZXN0X2N5Y2xpbmdfMjAxOS0wNC0xNl8wNS0yOS00Mi5jc3YuZml0JykKYGBgCgojIyMgV2hhdCBjYW4gd2UgZ2V0IGZyb20gVHJhaW5pbmdQZWFrcz8KCkxvdHMgb2YgbWV0YWRhdGEgZnJvbSB0aGUgZml0IGZpbGU6CiogIGZpbGUgaWQKKiAgZGV2aWNlIHNldHRpbmdzCiogIHVzZXIgcHJvcGVydGllcwoqICBzcG9ydCAvIHNwb3J0IHR5cGUKKiBldGMuIAoKYGBge3IgZWNobyA9IEZhbHNlfQpuYW1lcyhkYXRhKQpgYGAKCkFuZCBtb3JlIHNwZWNpZmljcyBhYm91dCB0aGUgYWN0aXZpdHk6CgpgYGB7ciBlY2hvID0gRmFsc2V9CmhlYWQoZGF0YSRyZWNvcmQpCmBgYAoKV2l0aCB0aGVpciBjb3JyZXNwb25kaW5nIHVuaXRzOgoKYGBge3J9Cm1hdHJpeChjKGF0dHIoZGF0YSRyZWNvcmQsJ25hbWVzJyksYXR0cihkYXRhJHJlY29yZCwndW5pdHMnKSksbmNvbD0yKQpgYGAKCiMjIyBCYXNpYyBQbG90cwoKIyMjIyBFbGV2YXRpb24gdnMgVGltZQoKYGBge3IgZmlnLndpZHRoID0gNn0KcGRhdGEgPC0gd2l0aChkYXRhJHJlY29yZCwgZGF0YS5mcmFtZShhbHQgPSBhbHRpdHVkZSwgdGltZSA9ICh0aW1lc3RhbXAtdGltZXN0YW1wWzFdKS82MCkpCiBnZ3Bsb3QocGRhdGEsIGFlcyh5PWFsdCwgeD10aW1lKSkgKyBnZW9tX2xpbmUoKSArCiAgIGdndGl0bGUoIkVsZXZhdGlvbiB2cyBUaW1lIikgKyB4bGFiKCJ0aW1lIChtaW51dGVzKSIpICsgeWxhYigiZWxldmF0aW9uIChtKSIpCmBgYAoKIyMjIyBFbGV2YXRpb24gdnMgRGlzdGFuY2UKCmBgYHtyIGZpZy53aWR0aCA9IDZ9CnBkYXRhIDwtIHdpdGgoZGF0YSRyZWNvcmQsIGRhdGEuZnJhbWUoYWx0ID0gYWx0aXR1ZGUsIHRpbWUgPSAoZGlzdGFuY2UtZGlzdGFuY2VbMV0pLzEwMDApKQogZ2dwbG90KHBkYXRhLCBhZXMoeT1hbHQsIHg9dGltZSkpICsgZ2VvbV9saW5lKCkgKwogICBnZ3RpdGxlKCJFbGV2YXRpb24gdnMgRGlzdGFuY2UiKSArIHhsYWIoIkRpc3RhbmNlIChrbSkiKSArIHlsYWIoImVsZXZhdGlvbiAobSkiKQpgYGAKCiMjIyMgU3VtbWFyeSBvZiBhbGwgdmFyaWFibGVzIChTdHJhdmEtbGlrZSkKCmBgYHtyIGZpZy53aWR0aCA9IDZ9CnBkYXRhIDwtIHdpdGgoZGF0YSRyZWNvcmQsIGRhdGEuZnJhbWUoYWx0ID0gYWx0aXR1ZGUsIHRpbWUgPSAoZGlzdGFuY2UtZGlzdGFuY2VbMV0pLzEwMDAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcG93ID0gcG93ZXIsIGhyPWhlYXJ0X3JhdGUsIHNwZWVkPXNwZWVkLCBjYWQ9Y2FkZW5jZSkpCnBkYXRhIDwtIHBkYXRhWy0oMToxMCksXQpwZGF0YSRncmFkIDwtIHdpdGgoZGF0YSRyZWNvcmQsIDEwMCAqIGRpZmYoYWx0aXR1ZGUsbGFnPTEwKSAvIGRpZmYoZGlzdGFuY2UsbGFnPTEwKSkKCiBnZ3Bsb3QocGRhdGEsIGFlcyh5PWFsdCwgeD10aW1lKSkgKwogICBnZW9tX2xpbmUoYWVzKHk9YWx0LGNvbG91cj0iQWx0aXR1ZGUiKSkgKwogICBnZW9tX2xpbmUoYWVzKHk9cG93KjIsY29sb3VyPSJQb3dlciIpLHNpemU9MC4wNSkgKwogICBnZW9tX2xpbmUoYWVzKHk9aHIqMixjb2xvdXI9IkhSIiksc2l6ZT0wLjEpICsKICAgZ2VvbV9saW5lKGFlcyh5PXNwZWVkKjMuNjkqMixjb2xvdXI9IlNwZWVkIiksc2l6ZT0wLjEpICsKICAgZ2VvbV9saW5lKGFlcyh5PWNhZCoyLGNvbG91cj0iQ2FkZW5jZSIpLHNpemU9MC4wNSkgKwogICBnZW9tX2xpbmUoYWVzKHk9Z3JhZCoxMCttaW4ocGRhdGEkYWx0KSxjb2xvdXI9IkdyYWRpZW50Iiksc2l6ZT0wLjIpICsKICAgIyBnZW9tX2FibGluZShpbnRlcmNlcHQ9bWluKHBkYXRhJGFsdCksIHNsb3BlPTAsY29sb3I9Im9yYW5nZSIsc2l6ZT0xLGx0eT0ibG9uZ2Rhc2giKSArCiAgIGdlb21fc2VnbWVudChhZXMoeCA9IDAsIHhlbmQgPSBtYXgocGRhdGEkdGltZSksIHkgPSBtaW4ocGRhdGEkYWx0KSwgeWVuZCA9IG1pbihwZGF0YSRhbHQpKSxjb2xvcj0ib3JhbmdlIixzaXplPTAuOCkgKwogICBzY2FsZV95X2NvbnRpbnVvdXMoc2VjLmF4aXMgPSBzZWNfYXhpcyh+Li8yLCBuYW1lID0gIkhSIChicG0pIC8gUG93ZXIgKFcpIC8gR3JhZGllbnQgKCUpIFxuIFNwZWVkIChrcGgpIC8gQ2FkZW5jZShycG0pIiksCiAgICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBzZXEoMCwgMTAwMCwgYnkgPSAyMDApKSArCiAgIHNjYWxlX3hfY29udGludW91cyhicmVha3M9IHNlcSgwLCA3MCwgYnkgPSAxMCkpICsKICAgZ2d0aXRsZSgiRGlzdGFuY2UgdnMgQWxsIikgKwogICB4bGFiKCJEaXN0YW5jZSAoa20pIikgKwogICB5bGFiKCJBbHRpdHVkZSAobSkiKSArCiAgIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gYygiYmxhY2siLCAicHVycGxlIiwib3JhbmdlIiwicmVkIiwiYmx1ZSIsImdyZWVuIikpICsKICAgbGFicyh5ID0gIkFsdGl0dWRlIChtKSIsCiAgICAgICAgICAgICAgICB4ID0gIkRpc3RhbmNlIChrbSkiLAogICAgICAgICAgICAgICAgY29sb3VyID0gIiIpICsKICAgdGhlbWVfbWluaW1hbCgpICsKICAgIyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSBjKDAuOSwgMC45MykpCiAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKQpgYGAKCiMjIyMgSGVhcnQgUmF0ZSB2cyBHcmFkaWVudAoKVGhlIHN0ZWVwZXN0IHRoZSBjbGltYiwgdGhlIGhhcmRlc3QgdGhlIGhlYXJ0IGhhcyB0byB3b3JrLgoKYGBge3IgZmlnLndpZHRoID0gNn0KcGRhdGEgPC0gZGF0YSRyZWNvcmRbLSgxOjEwKSxjKCJoZWFydF9yYXRlIiwidGltZXN0YW1wIildCiMgY29tcHV0ZSBhdmVyYWdlIGdyYWRpZW50LCBhcyAlCiBwZGF0YSRncmFkaWVudCA8LSB3aXRoKGRhdGEkcmVjb3JkLCAxMDAgKiBkaWZmKGFsdGl0dWRlLGxhZz0xMCkgLyBkaWZmKGRpc3RhbmNlLGxhZz0xMCkpCiBwZGF0YSA8LSBzdWJzZXQocGRhdGEsIGNvbXBsZXRlLmNhc2VzKHBkYXRhKSAmIGFicyhncmFkaWVudCkgPCA3LjUgJiBncmFkaWVudCAhPSAwKSAjIGRyb3Agb3V0bGllcnMKIGdncGxvdChwZGF0YSwgYWVzKHg9Z3JhZGllbnQsIHk9aGVhcnRfcmF0ZSkpICsKICAgZ2VvbV9wb2ludChhbHBoYT0wLjUpICsgZ2VvbV9qaXR0ZXIoKSArCiAgIHN0YXRfc21vb3RoKG1ldGhvZD0ibG0iLCBmb3JtdWxhPXkgfiBwb2x5KHgsIDIpKSArCiAgIGdndGl0bGUoIkhlYXJ0IHJhdGUgdnMgZ3JhZGllbnQiKQpgYGAKCldpdGggdGhlIGNvcnJlc3BvbmRpbmcgY29ycmVsYXRpb246CgpgYGB7ciBlY2hvID1GfQogZml0IDwtIGxtKGhlYXJ0X3JhdGUgfiBwb2x5KGdyYWRpZW50LCAyKSwgZGF0YT1wZGF0YSkKIHN1bW1hcnkoZml0KQpgYGAKCkFuZCBhdmVyYWdlcyBmb3IgZWFjaCBncmFkaWVudCBzdGVwOgoKYGBge3J9CnByZWQgPC0gZGF0YS5mcmFtZShncmFkaWVudCA9IHNlcSgtNiw2LDIpKQpwcmVkJGhlYXJ0X3JhdGUgPC0gcHJlZGljdChmaXQsIHByZWQpCnQocHJlZCkKYGBgCgojIyMgQWN0aXZpdHkgTWFwcwoKQ29udmVydCB0aW1lIHRvIG1pbnV0ZXMgYW5kIGFkZCBkaXJlY3Rpb24gKE91dGJvdW5kIHZzIFJldHVybik6CiAKYGBge3J9CiAjIHBvaW50cyA8LSBzdWJzZXQoZGF0YSRyZWNvcmQsIGNvbXBsZXRlLmNhc2VzKGRhdGEkcmVjb3JkKSkKcG9pbnRzIDwtIGRhdGEkcmVjb3JkCnBvaW50cyR0aW1lX21pbiAgPC0gd2l0aChwb2ludHMsIHRpbWVzdGFtcCAtIHRpbWVzdGFtcFsxXSkvNjAgIyBtaW51dGVzIG9mIHJpZGluZwoKIyBmcm9tIGRpYWdyYW0gYWJvdmUsIHdlIHR1cm5lZCBhcm91bmQgYXQgdGhlIDkwIG1pbnV0ZXMgbWFyawojIHBvaW50c1t3aGljaChwb2ludHMkYWx0aXR1ZGUgPT0gbWF4KHBvaW50cyRhbHRpdHVkZSkpLF0KcG9pbnRzJGRpcmVjdGlvbiA8LSB3aXRoKHBvaW50cywgZmFjdG9yKGlmZWxzZSh0aW1lX21pbiA8IDkwLCAnT3V0Ym91bmQnLCAnUmV0dXJuJykpKQpgYGAKCmBgYHtyfQogIyBsaWJyYXJ5KGxlYWZsZXQpCiAjIGxlYWZsZXQocG9pbnRzW3BvaW50cyRkaXJlY3Rpb24gPT0gJ091dGJvdW5kJyxdKSAlPiUgYWRkVGlsZXMoKSAlPiUgYWRkUG9seWxpbmVzKH5wb3NpdGlvbl9sb25nLH5wb3NpdGlvbl9sYXQpCiAjIGxlYWZsZXQocG9pbnRzW3BvaW50cyRkaXJlY3Rpb24gPT0gJ1JldHVybicsXSkgJT4lIGFkZFRpbGVzKCkgJT4lIGFkZFBvbHlsaW5lcyh+cG9zaXRpb25fbG9uZyx+cG9zaXRpb25fbGF0KQpgYGAKCiMjIyMgTWFwIHdpdGggSFIKClRoZSBkYXJrZXIgdGhlIGNvbG91ciwgdGhlIGhpZ2hlciB0aGUgSFIgaXM6CgpgYGB7cn0KbmV3Y29scyA8LSByZXYoY2V0X3BhbChtaW4oZGltKHBvaW50cylbMV0sMjU2KSxuYW1lPSJsMyIpKQpwb2ludHMgPC0gcG9pbnRzICU+JSBtdXRhdGUocXVhbnRpbGU9bnRpbGUoaGVhcnRfcmF0ZSwyNTYpKQpuZXdjb2xzLnF1YW50aWxlIDwtIG5ld2NvbHNbcG9pbnRzJHF1YW50aWxlXQpwb2ludHMxIDwtIHBvaW50cyAlPiUKIG11dGF0ZShuZXh0TGF0ID0gbGVhZChwb3NpdGlvbl9sYXQpLAogICAgICAgIG5leHRMbmcgPSBsZWFkKHBvc2l0aW9uX2xvbmcpLAogICAgICAgIGNvbG9yID0gbmV3Y29scy5xdWFudGlsZQogICAgICAgICkKZ3JhZGllbnRfbWFwIDwtIGxlYWZsZXQoKSAlPiUKIGFkZFRpbGVzKCkKIyBwb2ludHMxIDwtIHBvaW50czFbcG9pbnRzMSRkaXJlY3Rpb24gPT0gJ091dGJvdW5kJyxdCmZvciAoaSBpbiAxOm5yb3cocG9pbnRzMSkpIHsKIGdyYWRpZW50X21hcCA8LSBhZGRQb2x5bGluZXMobWFwID0gZ3JhZGllbnRfbWFwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gcG9pbnRzMSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbG5nID0gYXMubnVtZXJpYyhwb2ludHMxW2ksIGMoJ3Bvc2l0aW9uX2xvbmcnLCAnbmV4dExuZycpXSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhdCA9IGFzLm51bWVyaWMocG9pbnRzMVtpLCBjKCdwb3NpdGlvbl9sYXQnLCAnbmV4dExhdCcpXSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gYXMuY2hhcmFjdGVyKHBvaW50czFbaSwgYygnY29sb3InKV0pCiApCn0KZ3JhZGllbnRfbWFwCmBgYAoKIyMjIyBNYXAgd2l0aCBIUiBhbmQgcHJvZmlsZQoKQW5kIHdlIGNhbiBvdmVybGFwIGRpZmZlcmVudCBtZWFzdXJlbWVudHMgdG8gdGhlIHNhbWUgcGxvdC4KVGhlIGRhcmtlciB0aGUgY29sb3VyLCB0aGUgaGlnaGVyIHRoZSBIUiBhbmQgdGhlIGhpZ2hlciB0aGUgYWx0aXR1ZGUgKGZvciB0aGUgbmV3IGxpbmUpOgoKCmBgYHtyfQpuZXdjb2xzIDwtIHJldihjZXRfcGFsKG1pbihkaW0ocG9pbnRzKVsxXSwyNTYpLG5hbWU9ImwzIikpCm5ld2NvbHNfYWx0IDwtIHJldihjZXRfcGFsKG1pbihkaW0ocG9pbnRzKVsxXSwyNTYpLG5hbWU9Imw3IikpCnBvaW50cyA8LSBwb2ludHMgJT4lIG11dGF0ZShxdWFudGlsZT1udGlsZShoZWFydF9yYXRlLDI1NikpCnBvaW50cyA8LSBwb2ludHMgJT4lIG11dGF0ZShxdWFudGlsZV9hbHQ9bnRpbGUoYWx0aXR1ZGUsMjU2KSkKbmV3Y29scy5xdWFudGlsZSA8LSBuZXdjb2xzW3BvaW50cyRxdWFudGlsZV0KcG9pbnRzMSA8LSBwb2ludHMgJT4lCiBtdXRhdGUobmV4dExhdCA9IGxlYWQocG9zaXRpb25fbGF0KSwKICAgICAgICBuZXh0TG5nID0gbGVhZChwb3NpdGlvbl9sb25nKSwKICAgICAgICBjb2xvciA9IG5ld2NvbHMucXVhbnRpbGUsCiAgICAgICAgY29sb3JfYWx0ID0gbmV3Y29sc19hbHRbcG9pbnRzJHF1YW50aWxlX2FsdF0KICAgICAgICApCmdyYWRpZW50X21hcCA8LSBsZWFmbGV0KCkgJT4lIGFkZFRpbGVzKCkKIyBwb2ludHMxIDwtIHBvaW50czFbcG9pbnRzMSRkaXJlY3Rpb24gPT0gJ091dGJvdW5kJyxdCmZvciAoaSBpbiAxOm5yb3cocG9pbnRzMSkpIHsKIyBmb3IgKGkgaW4gMTo1MDApIHsKIGdyYWRpZW50X21hcCA8LSBhZGRQb2x5bGluZXMobWFwID0gZ3JhZGllbnRfbWFwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gcG9pbnRzMSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbG5nID0gYXMubnVtZXJpYyhwb2ludHMxW2ksIGMoJ3Bvc2l0aW9uX2xvbmcnLCAnbmV4dExuZycpXSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhdCA9IGFzLm51bWVyaWMocG9pbnRzMVtpLCBjKCdwb3NpdGlvbl9sYXQnLCAnbmV4dExhdCcpXSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gYXMuY2hhcmFjdGVyKHBvaW50czFbaSwgYygnY29sb3InKV0pCiApCn0KCmZvciAoaSBpbiAxOndoaWNoLm1heChwb2ludHMxJHF1YW50aWxlX2FsdCkpIHsKIyBmb3IgKGkgaW4gMTo1MDApIHsKIGdyYWRpZW50X21hcCA8LSBhZGRQb2x5bGluZXMobWFwID0gZ3JhZGllbnRfbWFwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gcG9pbnRzMSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbG5nID0gYXMubnVtZXJpYyhwb2ludHMxW2ksIGMoJ3Bvc2l0aW9uX2xvbmcnLCAnbmV4dExuZycpXSktMC4wMDUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhdCA9IGFzLm51bWVyaWMocG9pbnRzMVtpLCBjKCdwb3NpdGlvbl9sYXQnLCAnbmV4dExhdCcpXSktMC4wMDUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gYXMuY2hhcmFjdGVyKHBvaW50czFbaSwgYygnY29sb3JfYWx0JyldKQogKQp9CgpncmFkaWVudF9tYXAKYGBgCgojIyMjIE1hcCBvZiBhIGRpZmZlcmVudCBhY3Rpdml0eQoKRnVuY3Rpb24gdG8gY3JlYXRlIG1hcDoKCmBgYHtyfQpjcmVhdGVfbWFwX2ZpdCA8LSBmdW5jdGlvbiAoZml0X2RhdGEsYWx0KXsKICBwb2ludHMgPC0gZml0X2RhdGEkcmVjb3JkCiAgbmV3Y29scyA8LSByZXYoY2V0X3BhbChtaW4oZGltKHBvaW50cylbMV0sMjU2KSxuYW1lPSJsMyIpKQogIG5ld2NvbHNfYWx0IDwtIHJldihjZXRfcGFsKG1pbihkaW0ocG9pbnRzKVsxXSwyNTYpLG5hbWU9Imw3IikpCiAgcG9pbnRzIDwtIHBvaW50cyAlPiUgbXV0YXRlKHF1YW50aWxlPW50aWxlKGhlYXJ0X3JhdGUsMjU2KSkKICBpZiAoYWx0ID09IFRSVUUpIHsKICAgIHBvaW50cyA8LSBwb2ludHMgJT4lIG11dGF0ZShxdWFudGlsZV9hbHQ9bnRpbGUoYWx0aXR1ZGUsMjU2KSkKICB9CiAgaWYgKGFsdCA9PSBUUlVFKSB7CiAgICBwb2ludHMxIDwtIHBvaW50cyAlPiUgbXV0YXRlKG5leHRMYXQgPSBsZWFkKHBvc2l0aW9uX2xhdCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuZXh0TG5nID0gbGVhZChwb3NpdGlvbl9sb25nKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gbmV3Y29sc1twb2ludHMkcXVhbnRpbGVdLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3JfYWx0ID0gbmV3Y29sc19hbHRbcG9pbnRzJHF1YW50aWxlX2FsdF0pCiAgfSBlbHNlIHsKICAgIHBvaW50czEgPC0gcG9pbnRzICU+JSBtdXRhdGUobmV4dExhdCA9IGxlYWQocG9zaXRpb25fbGF0KSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5leHRMbmcgPSBsZWFkKHBvc2l0aW9uX2xvbmcpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSBuZXdjb2xzW3BvaW50cyRxdWFudGlsZV0pCiAgfQogIAogICNwcmVwYXJlIG1hcAogIGdyYWRpZW50X21hcCA8LSBsZWFmbGV0KCkgJT4lIGFkZFRpbGVzKCkKICBmb3IgKGkgaW4gMTpucm93KHBvaW50czEpKSB7CiAgIGdyYWRpZW50X21hcCA8LSBhZGRQb2x5bGluZXMobWFwID0gZ3JhZGllbnRfbWFwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSBwb2ludHMxLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxuZyA9IGFzLm51bWVyaWMocG9pbnRzMVtpLCBjKCdwb3NpdGlvbl9sb25nJywgJ25leHRMbmcnKV0pLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhdCA9IGFzLm51bWVyaWMocG9pbnRzMVtpLCBjKCdwb3NpdGlvbl9sYXQnLCAnbmV4dExhdCcpXSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSBhcy5jaGFyYWN0ZXIocG9pbnRzMVtpLCBjKCdjb2xvcicpXSkpCiAgfQogIGlmIChhbHQgPT0gVFJVRSkgewogICAgIyBmb3IgKGkgaW4gMTp3aGljaC5tYXgocG9pbnRzMSRxdWFudGlsZV9hbHQpKSB7CiAgICBmb3IgKGkgaW4gMTpucm93KHBvaW50czEpKSB7CiAgICAgZ3JhZGllbnRfbWFwIDwtIGFkZFBvbHlsaW5lcyhtYXAgPSBncmFkaWVudF9tYXAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gcG9pbnRzMSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxuZyA9IGFzLm51bWVyaWMocG9pbnRzMVtpLCBjKCdwb3NpdGlvbl9sb25nJywgJ25leHRMbmcnKV0pLTAuMDA1LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGF0ID0gYXMubnVtZXJpYyhwb2ludHMxW2ksIGMoJ3Bvc2l0aW9uX2xhdCcsICduZXh0TGF0JyldKS0wLjAwNSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gYXMuY2hhcmFjdGVyKHBvaW50czFbaSwgYygnY29sb3JfYWx0JyldKSkKICAgIH0KICB9CiAgZ3JhZGllbnRfbWFwCn0KYGBgCgpgYGB7cn0KdCA8LSByZWFkLmZpdCgiU0VMRUNURURfRklMRVMvV2hpdGVMaWFtLzA5L1doaXRlTDE5OTQuMjAxOS0wOS0yMC0yMy0wNS0yOS0yOTNaLkdhcm1pblB1c2guNDA5NjQ4MDU3MDUuZml0IikKY3JlYXRlX21hcF9maXQodCxhbHQ9VFJVRSkKYGBgCgoqKioKCiMgVHJhaW5pbmdQZWFrcyBBbmFseXNpcwoKIyMgRXh0cmFjdCBwcmVkaWN0b3JzIGZyb20gYWN0aXZpdGllcwoKV2Ugd2FudCB0byBjYWxjdWxhdGUgZGlmZmVyZW50IHByZWRpY3RvcnMgYmFzZWQgb24gdGhlIHRyYWluaW5nIGFjdGl2aXR5OgpQaGFzZSAxIHdpbGwgY29udGFpbjoKIAoqIE51bWJlciBvZiBBY3Rpdml0aWVzCiogVm9sdW1lIChUaW1lKQoqIEludGVuc2l0eQogICogJUhSbWF4CiAgKiBUaHJlc2hvbGRzIChUaW1lIGluIFQxL1QyKQogICogVm9sdW1lIHggJUhSbWF4CiogU2Vzc2lvbnMgcGVyIHdlZWsKKiBBdmVyYWdlIHRpbWUKKiBBdmVyYWdlIHNwZWVkIChjeWNsaW5nIHZzIHJ1bm5pbmcsIGV0Yz8pCiAgKiBFdmVudCB0eXBlID0gMSA9PSBjeWNsaW5nPz8KICAqIHNwb3J0ID0gMj8KICAgICogMiA9IGN5Y2xpbmcKICAgICogNSA9IHN3aW1taW5nCiAgKiBodHRwczovL2RldmVsb3Blci5nYXJtaW4uY29tL2Rvd25sb2Fkcy9jb25uZWN0LWlxL21vbmtleS1jL2RvYy9Ub3lib3gvQWN0aXZpdHlSZWNvcmRpbmcuaHRtbAoqIEF2ZXJhZ2UgSFIKKiBIZWFydCBiZWF0cyBwZXIgd2Vlay95ZWFyIG9mIHRyYWluaW5nCiogQXZlcmFnZSBhY2N1bXVsYXRlZCBwb3dlcgoqIERhdGEgZnJvbSBUcmFpbmluZ1BlYWtzPyAoZGF0YSRzZXNzaW9uKQogICogQ2Fsb3JpZXMKICAqIFRvdGFsIGFzY2VudAogICogbWF4X3Bvd2VyCiAgKiBhdmVyYWdlX3Bvd2VyIC8gbm9ybWFsaXplZF9wb3dlcgogICogdG90YWwgd29yawogICogdHJhaW5pbmdfc3RyZXNzX3Njb3JlCiAgKiB0b3RhbF9kaXN0YW5jZQogICogaW50ZW5zaXR5X2ZhY3RvcgogICogYXZnX0hSCgojIyMgUHJlZGljdG9ycwoKUmVhZCBhY3Rpdml0eSBkYXRhCihPbmx5IDEgZm9yIHRoZSBtb21lbnQsIGJ1dCB3aWxsIGxvb3AgdGhyb3VnaCBhbGwgYWN0aXZpdGllcyBsYXRlcik6CgpgYGB7cn0KIyBkYXRhIDwtIHJlYWQuZml0KCdmaXQtdGVzdF9jeWNsaW5nXzIwMTktMDQtMTZfMDUtMjktNDIuY3N2LmZpdCcpCiMgZGF0YSA8LSByZWFkLmZpdCgnUkFXL1dvcmtvdXRGaWxlRXhwb3J0LVdoaXRlLUxpYW0tMjAxOS0wNC0wMS0yMDE5LTEwLTAxL1doaXRlTDE5OTQuMjAxOS0wNC0xNi0wNy01OC01NC01NzJaLkdhcm1pblB1c2guMzMxMDI4OTkxNzEuZml0JykKZGF0YSA8LSByZWFkLmZpdCgnUkFXL1dvcmtvdXRGaWxlRXhwb3J0LVdoaXRlLUxpYW0tMjAxOS0wNC0wMS0yMDE5LTEwLTAxL1doaXRlTDE5OTQuMjAxOS0wNC0xNy0wOC0zNC00MS0zNDNaLkdhcm1pblB1c2guMzMxNTA2OTY4NjkuZml0JykKYmFkZGF0YSA8LSByZWFkLmZpdCgiU0VMRUNURURfRklMRVMvV2hpdGVMaWFtLzA5L1doaXRlTDE5OTQuMjAxOS0wOS0xMi0wNy0wMS0wNy03NTlaLkdhcm1pblB1c2guNDA0ODkzMDgwMjMuZml0IikKYGBgCgpJbml0aWFsaXplIHByZWRpY3RvcnMgZGYgd2l0aCBhY3Rpdml0eSBpZDoKCiogIE51bWJlciBvZiBBY3Rpdml0aWVzCgpgYGB7cn0KcHJlZGljdG9ycyA8LSBhcy5kYXRhLmZyYW1lKG1hdHJpeCgxLG5jb2w9MSkpCmNvbG5hbWVzKHByZWRpY3RvcnMpIDwtIGMoImFjdGl2aXR5LmlkIikKYGBgCgoqIFZvbHVtZSAoVGltZSkKCmBgYHtyfQphY3Rpdml0eS50aW1lIDwtIHJvdW5kKChtYXgoZGF0YSRyZWNvcmQkdGltZXN0YW1wKS1kYXRhJHJlY29yZCR0aW1lc3RhbXBbMV0pLzYwLDIpCnByZWRpY3RvcnMkdGltZS5taW4gPC0gYWN0aXZpdHkudGltZQpgYGAKCiogSW50ZW5zaXR5CiAgKiAlSFJtYXgKICAqIFZvbHVtZSB4ICVIUm1heAogIApgYGB7cn0KcHJlZGljdG9ycyRocm1heC5hdGhsZXRlIDwtIGRhdGEkem9uZXNfdGFyZ2V0JG1heF9oZWFydF9yYXRlCnByZWRpY3RvcnMkaHJtYXguYWN0aXZpdHkgPC0gbWF4KGRhdGEkcmVjb3JkJGhlYXJ0X3JhdGUpCnByZWRpY3RvcnMkaHJtYXgucGVyYyA8LSByb3VuZChwcmVkaWN0b3JzJGhybWF4LmFjdGl2aXR5L3ByZWRpY3RvcnMkaHJtYXguYXRobGV0ZSoxMDAsMikKcHJlZGljdG9ycyRocm1heC5pbnRlbnNpdHkgPC0gcHJlZGljdG9ycyRocm1heC5wZXJjICogcHJlZGljdG9ycyR0aW1lLm1pbgpgYGAKICAKICAqIFRocmVzaG9sZHMgKFRpbWUgaW4gWjUvWjQvWjMpCmh0dHBzOi8vYmxvZ3Muc2FzLmNvbS9jb250ZW50L2Vmcy8yMDE4LzAxLzI2L2RhdGEtZHJpdmVuLWZpdG5lc3Mtdm8yLW1heC1sYWN0YXRlLXRocmVzaG9sZC1oZWFydC1yYXRlLwoKYGBge3J9CmhyLnpvbmVzIDwtIHF1YW50aWxlKGMoMTpwcmVkaWN0b3JzJGhybWF4LmF0aGxldGUpLHByb2JzPXNlcSgwLDEsYnk9MC4xKSkKIyB3ZSB3YW50IHpvbmVzIDEgdG8gNQpkYXRhJHJlY29yZCRoci56b25lcyA8LSBmaW5kSW50ZXJ2YWwoZGF0YSRyZWNvcmQkaGVhcnRfcmF0ZSxoci56b25lc1s2OjEwXSkKaHIuem9uZXMudGFibGUgPC0gcm91bmQodGFibGUoZGF0YSRyZWNvcmQkaHIuem9uZXMpL3N1bSh0YWJsZShkYXRhJHJlY29yZCRoci56b25lcykpKjEwMCwxKQpwcmVkaWN0b3JzJGhyLno1IDwtIGhyLnpvbmVzLnRhYmxlWzZdCnByZWRpY3RvcnMkaHIuejQgPC0gaHIuem9uZXMudGFibGVbNV0KcHJlZGljdG9ycyRoci56MyA8LSBoci56b25lcy50YWJsZVs0XQpwcmVkaWN0b3JzJGhyLnoyIDwtIGhyLnpvbmVzLnRhYmxlWzNdCnByZWRpY3RvcnMkaHIuejEgPC0gaHIuem9uZXMudGFibGVbMl0KYGBgCiAgCiogU2Vzc2lvbnMgcGVyIHdlZWsgKEFUSExFVEUgQ0FMQ1VMQVRJT04pCldpbGwgYWRkIGEgd2VlayBjb3VudGVyCgpgYGB7cn0KcHJlZGljdG9ycyR3ZWVrIDwtIDEKYGBgCgoqIEF2ZXJhZ2UgSFIKCmBgYHtyfQojIHByZWRpY3RvcnMkaHIuYXZnIDwtIG1lYW4oZGF0YSRyZWNvcmQkaGVhcnRfcmF0ZSkKcHJlZGljdG9ycyRoci5hdmcgPC0gZGF0YSRzZXNzaW9uJGF2Z19oZWFydF9yYXRlCmBgYAoKKiBBdmVyYWdlIHNwZWVkIChjeWNsaW5nIHZzIHJ1bm5pbmcsIGV0Yz8pCiAgKiBFdmVudCB0eXBlID0gMSA9PSBjeWNsaW5nPz8KICAqIHNwb3J0ID0gMj8KCmBgYHtyfQpzcG9ydF9jb2RlIDwtIGMoMCwxLDIsNSwxMCwxNSkKc3BvcnRfdHlwZSA8LSBjKCJVbmRlZmluZWQiLCJSdW5uaW5nIiwiQ3ljbGluZyIsIlN3aW1taW5nIiwiUm93aW5nIikKcHJlZGljdG9ycyRzcG9ydF9jb2RlIDwtIGRhdGEkc2Vzc2lvbiRzcG9ydApwcmVkaWN0b3JzJHNwb3J0X3R5cGUgPC0gc3BvcnRfdHlwZVt3aGljaCAoc3BvcnRfY29kZSA9PSBwcmVkaWN0b3JzJHNwb3J0X2NvZGUpXQpwcmVkaWN0b3JzJHNwZWVkLmF2ZyA8LSByb3VuZChkYXRhJHNlc3Npb24kYXZnX3NwZWVkKjMuNzksMSkKYGBgCiAgCiAgKiBBdmVyYWdlIGFjY3VtdWxhdGVkIHBvd2VyIChBVEhMRVRFIENBTENVTEFUSU9OKQoqIEhlYXJ0IGJlYXRzIHBlciB3ZWVrL3llYXIgb2YgdHJhaW5pbmcgKEFUSExFVEUgQ0FMQ1VMQVRJT04pCiogRGF0YSBmcm9tIFRyYWluaW5nUGVha3M/IChkYXRhJHNlc3Npb24pCiAgKiBDYWxvcmllcwpgYGB7cn0KcHJlZGljdG9ycyRjYWwgPC0gZGF0YSRzZXNzaW9uJHRvdGFsX2NhbG9yaWVzCmBgYAogICogVG90YWwgYXNjZW50CmBgYHtyfQpwcmVkaWN0b3JzJGFzY2VudCA8LSBkYXRhJHNlc3Npb24kdG90YWxfYXNjZW50CmBgYAoKICAqIG1heF9wb3dlcgogICogYXZlcmFnZV9wb3dlciAvIG5vcm1hbGl6ZWRfcG93ZXIKICAKYGBge3J9CnByZWRpY3RvcnMkcG93ZXIubWF4IDwtIGRhdGEkc2Vzc2lvbiRtYXhfcG93ZXIKcHJlZGljdG9ycyRwb3dlci5hdmcgPC0gZGF0YSRzZXNzaW9uJGF2Z19wb3dlcgpwcmVkaWN0b3JzJHBvd2VyLm5vcm0gPC0gZGF0YSRzZXNzaW9uJG5vcm1hbGl6ZWRfcG93ZXIKYGBgCiAgKiB0b3RhbCB3b3JrCmBgYHtyfQpwcmVkaWN0b3JzJHdvcmsgPC0gZGF0YSRzZXNzaW9uJHRvdGFsX3dvcmsKYGBgCiAgKiB0cmFpbmluZ19zdHJlc3Nfc2NvcmUKYGBge3J9CnByZWRpY3RvcnMkc3RyZXNzLnNjb3JlIDwtIGRhdGEkc2Vzc2lvbiR0cmFpbmluZ19zdHJlc3Nfc2NvcmUKYGBgCiAgKiB0b3RhbF9kaXN0YW5jZQpgYGB7cn0KcHJlZGljdG9ycyR0b3RhbC5kaXN0IDwtIGRhdGEkc2Vzc2lvbiR0b3RhbF9kaXN0YW5jZQpgYGAKICAqIGludGVuc2l0eV9mYWN0b3IKYGBge3J9CnByZWRpY3RvcnMkaW50ZW5zaXR5LmZhY3RvciA8LSBkYXRhJHNlc3Npb24kaW50ZW5zaXR5X2ZhY3RvcgpgYGAKCgpXaGljaCB3aWxsIGdpdmUgdXMgYSB0YWJsZSBhcyBmb2xsb3dzOgoKYGBge3J9CnByZWRpY3RvcnMKYGBgCgoqKiogCgojIyBTdWJzZXQgMTAgYXRobGV0ZXMKCiMjIyBBdGhsZXRlIG9yaWdpbmFsIGRhdGEKCldlIHdpbGwgaGF2ZSBWTzJtYXggZm9yIGFsbCBwYXJ0aWNpcGFudHMgKE5hbWUsIFZPMm1heCwgV2VpZ2h0KToKCmBgYHtyfQpsaWJyYXJ5KHB1cnJyKQp2bzJtYXggPC0gcmVhZC5jc3YoIlZPMm1heGRhdGEuY3N2IikKdm8ybWF4IDwtIHZvMm1heFt2bzJtYXgkRXZlbnROYW1lID09ICJCYXNlbGluZSIsXQpyb3cubmFtZXModm8ybWF4KSA8LSAxOm5yb3codm8ybWF4KQp2bzJtYXggPC0gdm8ybWF4ICU+JSBtdXRhdGUobmFtZT0gbWFwKHN0cnNwbGl0KGFzLmNoYXJhY3RlcihBdGhsZXRlKSwgIig/IV4pKD89W1s6dXBwZXI6XV0pIiwgcGVybD1UKSx+LnhbMV0pICU+JSB1bmxpc3QoKSkKdm8ybWF4CmBgYAoKIyMjIEF0aGxldGUgaW5wdXQgZmlsZXMKCkdldCBhbGwgZmlsZXMgZm9yIHRoZSAxMCBhdGhsZXRlcyAoU2VwdGVtYmVyIDIwMTkpOgoKYGBge3J9CmRpcnMgPC0gbGlzdC5kaXJzKCJTRUxFQ1RFRF9GSUxFUyIscmVjdXJzaXZlPUYpCmZvciAoYXRoIGluIHZvMm1heCRuYW1lKXsKICBrIDwtIHBhc3RlMChkaXJzW2dyZXAodG9sb3dlcihhdGgpLHRvbG93ZXIoZGlycykpXSwiLzA5IikKICBwcmludChrKQogIHByaW50KGhlYWQobGlzdC5maWxlcyhrKSwxKSkKfQpgYGAKCioqKiAKCiMjIFByZWRpY3RvcnMgZm9yIGFsbCBhY3Rpdml0aWVzCgpPdXIgZ29hbCBub3cgaXMgdG8gY3JlYXRlIGEgZnJhbWV3b3JrIHRoYXQgaW5wdXRzIGEgbGlzdCBvZiBmaWxlcyBhbmQgcmV0dXJucyBhIHRhYmxlIHdpdGggYWxsIHByZWRpY3RvcnMuCkZpcnN0IGZvciBhbGwgRml0IGZpbGVzIG9mIGF0aGxldGVzIGFuZCB0aGVuLCBjYWxjdWxhdGUgdG90YWxzIGFuZCBhdmVyYWdlcyBmb3IgZWFjaCBvZiB0aGUgYXRobGV0ZXMuCgojIyMgVGVzdCBDYXNlIChXaGl0ZUxpYW0pCgpHZXQgYWxsIFdoaXRlTGlhbSBmaWxlcwoKYGBge3J9CndoaXRlZmlsZXMgPC0gcGFzdGUwKHBhc3RlMChkaXJzW2dyZXAoImxpYW0iLHRvbG93ZXIoZGlycykpXSwiLzA5LyIpLGxpc3QuZmlsZXMocGFzdGUwKGRpcnNbZ3JlcCgibGlhbSIsdG9sb3dlcihkaXJzKSldLCIvMDkiKSkpCmBgYAoKQW5kIGNyZWF0ZSBhIGZ1bmN0aW9uIHRvIGNhbGN1bGF0ZSBhbGwgcHJlZGljdG9ycyBhcyBwcmV2aW91c2x5LCBnaXZlbiBhIGxpc3Qgb2YgRml0IGZpbGVzOgoKYGBge3J9CmNhbGNfcHJlZGljdG9ycyA8LSBmdW5jdGlvbiAoZml0RmlsZXMpIHsKICAjaW5pdGlhbGl6ZSBwcmVkaWN0b3JzCiAgYWxsLnByZWRpY3RvcnMgPC0gYXMuZGF0YS5mcmFtZShtYXRyaXgodmVjdG9yKCksbmNvbD0yNSkpCiAgY29sbmFtZXMoYWxsLnByZWRpY3RvcnMpIDwtIGMoImFjdGl2aXR5LmlkIiwidGltZS5taW4iLCJocm1heC5hdGhsZXRlIiwiaHJtYXguYWN0aXZpdHkiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgImhybWF4LnBlcmMiLCJocm1heC5pbnRlbnNpdHkiLCJoci56NSIsImhyLno0IiwiaHIuejMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgImhyLnoyIiwiaHIuejEiLCJ3ZWVrIiwiaHIuYXZnIiwic3BvcnRfY29kZSIsInNwb3J0X3R5cGUiLCJzcGVlZC5hdmciLCJjYWwiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgImFzY2VudCIsInBvd2VyLm1heCIsInBvd2VyLmF2ZyIsInBvd2VyLm5vcm0iLCJ3b3JrIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICJzdHJlc3Muc2NvcmUiLCJ0b3RhbC5kaXN0IiwiaW50ZW5zaXR5LmZhY3RvciIpCiAgYWN0aXZpdHkuaWQgPSAxCiAgZm9yIChteWZpbGUgaW4gZml0RmlsZXMpIHsKICAgICAjIHByaW50KG15ZmlsZSkKICAgICBkYXRhIDwtIHJlYWQuZml0KG15ZmlsZSkKICAgICAjY2hlY2sgaWYgdGhlcmUgYXJlIE5BcyBpbiBkYXRhJHJlY29yZCAoc29tZSBjYXNlcyBzZWVuKSAtLT4gSmVubmVyU2FtdWVsIFs1XQogICAgICMgaWYgdAogICAgIHByZWRpY3RvcnMgPC0gYXMuZGF0YS5mcmFtZShtYXRyaXgoTkEsbmNvbD0yNSkpCiAgICAgY29sbmFtZXMocHJlZGljdG9ycykgPC0gYygiYWN0aXZpdHkuaWQiLCJ0aW1lLm1pbiIsImhybWF4LmF0aGxldGUiLCJocm1heC5hY3Rpdml0eSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAiaHJtYXgucGVyYyIsImhybWF4LmludGVuc2l0eSIsImhyLno1IiwiaHIuejQiLCJoci56MyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAiaHIuejIiLCJoci56MSIsIndlZWsiLCJoci5hdmciLCJzcG9ydF9jb2RlIiwic3BvcnRfdHlwZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAic3BlZWQuYXZnIiwiY2FsIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICJhc2NlbnQiLCJwb3dlci5tYXgiLCJwb3dlci5hdmciLCJwb3dlci5ub3JtIiwid29yayIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAic3RyZXNzLnNjb3JlIiwidG90YWwuZGlzdCIsImludGVuc2l0eS5mYWN0b3IiKQogICAgIGFjdGl2aXR5LnRpbWUgPC0gcm91bmQoKG1heChkYXRhJHJlY29yZCR0aW1lc3RhbXApLWRhdGEkcmVjb3JkJHRpbWVzdGFtcFsxXSkvNjAsMikKICAgICBwcmVkaWN0b3JzJHRpbWUubWluIDwtIGFjdGl2aXR5LnRpbWUKICAgICBwcmVkaWN0b3JzJGFjdGl2aXR5LmlkIDwtIGFjdGl2aXR5LmlkCiAgICAgI2NoZWNrIGlmIHRoZXJlIGlzIGhlYXJ0X3JhdGUgaW5mbyAobm90IGluIHN3aW1taW5nLCBzdXJmaW5nIGFuZCBvdGhlciB3YXRlcnNwb3J0cykKICAgICBpZihpcy5udWxsKGRhdGEkem9uZXNfdGFyZ2V0JG1heF9oZWFydF9yYXRlKSl7CiAgICAgICBwcmVkaWN0b3JzJGhybWF4LmF0aGxldGUgPC0gMjAwCiAgICAgfSBlbHNlIHsKICAgICAgIHByZWRpY3RvcnMkaHJtYXguYXRobGV0ZSA8LSBkYXRhJHpvbmVzX3RhcmdldCRtYXhfaGVhcnRfcmF0ZQogICAgIH0KICAgICBpZiAoYW55KG5hbWVzKGRhdGEkcmVjb3JkKSAlaW4lICJoZWFydF9yYXRlIikpewogICAgICAgcHJlZGljdG9ycyRocm1heC5hY3Rpdml0eSA8LSBtYXgoZGF0YSRyZWNvcmQkaGVhcnRfcmF0ZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgWzE6KGxlbmd0aChkYXRhJHJlY29yZCRoZWFydF9yYXRlKS0zKV0sbmEucm09VCkKICAgICAgIHByZWRpY3RvcnMkaHJtYXgucGVyYyA8LSByb3VuZChwcmVkaWN0b3JzJGhybWF4LmFjdGl2aXR5L3ByZWRpY3RvcnMkaHJtYXguYXRobGV0ZSoxMDAsMikKICAgICAgIHByZWRpY3RvcnMkaHJtYXguaW50ZW5zaXR5IDwtIHByZWRpY3RvcnMkaHJtYXgucGVyYyAqIHByZWRpY3RvcnMkdGltZS5taW4KICAgICAgIGhyLnpvbmVzIDwtIHF1YW50aWxlKGMoMTpwcmVkaWN0b3JzJGhybWF4LmF0aGxldGUpLHByb2JzPXNlcSgwLDEsYnk9MC4xKSkKICAgICAgIGRhdGEkcmVjb3JkJGhyLnpvbmVzIDwtIGZpbmRJbnRlcnZhbChkYXRhJHJlY29yZCRoZWFydF9yYXRlLGhyLnpvbmVzWzY6MTBdKQogICAgICAgaHIuem9uZXMudGFibGU8LXJvdW5kKHRhYmxlKGRhdGEkcmVjb3JkJGhyLnpvbmVzKS9zdW0odGFibGUoZGF0YSRyZWNvcmQkaHIuem9uZXMpKSoxMDAsMSkKICAgICAgIHByZWRpY3RvcnMkaHIuejUgPC0gaHIuem9uZXMudGFibGVbNl0KICAgICAgIHByZWRpY3RvcnMkaHIuejQgPC0gaHIuem9uZXMudGFibGVbNV0KICAgICAgIHByZWRpY3RvcnMkaHIuejMgPC0gaHIuem9uZXMudGFibGVbNF0KICAgICAgIHByZWRpY3RvcnMkaHIuejIgPC0gaHIuem9uZXMudGFibGVbM10KICAgICAgIHByZWRpY3RvcnMkaHIuejEgPC0gaHIuem9uZXMudGFibGVbMl0KICAgICAgIHByZWRpY3RvcnMkaHIuYXZnIDwtIGRhdGEkc2Vzc2lvbiRhdmdfaGVhcnRfcmF0ZQogICAgIH0gZWxzZSB7CiAgICAgICBwcmVkaWN0b3JzJGhybWF4LmF0aGxldGUgPC0gTkEKICAgICAgIHByZWRpY3RvcnMkaHJtYXguYWN0aXZpdHkgPC0gTkEKICAgICAgIHByZWRpY3RvcnMkaHJtYXgucGVyYyA8LSBOQQogICAgICAgcHJlZGljdG9ycyRocm1heC5pbnRlbnNpdHkgPC0gTkEKICAgICAgIHByZWRpY3RvcnMkaHIuejUgPC0gTkEKICAgICAgIHByZWRpY3RvcnMkaHIuejQgPC0gTkEKICAgICAgIHByZWRpY3RvcnMkaHIuejMgPC0gTkEKICAgICAgIHByZWRpY3RvcnMkaHIuejIgPC0gTkEKICAgICAgIHByZWRpY3RvcnMkaHIuejEgPC0gTkEKICAgICAgIHByZWRpY3RvcnMkaHIuYXZnIDwtIE5BCiAgICAgfQogICAgIHByZWRpY3RvcnMkd2VlayA8LSAxCiAgICAgcHJlZGljdG9ycyRzcG9ydF9jb2RlIDwtIGRhdGEkc2Vzc2lvbiRzcG9ydAogICAgIHByZWRpY3RvcnMkc3BvcnRfdHlwZSA8LSBzcG9ydF90eXBlW3doaWNoIChzcG9ydF9jb2RlID09IHByZWRpY3RvcnMkc3BvcnRfY29kZSldCiAgICAgcHJlZGljdG9ycyRzcGVlZC5hdmcgPC0gcm91bmQoZGF0YSRzZXNzaW9uJGF2Z19zcGVlZCozLjc5LDEpCiAgICAgcHJlZGljdG9ycyRjYWwgPC0gaWYoaXMubnVsbChkYXRhJHNlc3Npb24kdG90YWxfY2Fsb3JpZXMpKXtOQX1lbHNle2RhdGEkc2Vzc2lvbiR0b3RhbF9jYWxvcmllc30KICAgICBwcmVkaWN0b3JzJGFzY2VudCA8LSBpZihpcy5udWxsKGRhdGEkc2Vzc2lvbiR0b3RhbF9hc2NlbnQpKXtOQX1lbHNle2RhdGEkc2Vzc2lvbiR0b3RhbF9hc2NlbnR9CiAgICAgcHJlZGljdG9ycyRwb3dlci5tYXggPC0gaWYoaXMubnVsbChkYXRhJHNlc3Npb24kbWF4X3Bvd2VyKSl7TkF9ZWxzZXtkYXRhJHNlc3Npb24kbWF4X3Bvd2VyfQogICAgIHByZWRpY3RvcnMkcG93ZXIuYXZnIDwtIGlmKGlzLm51bGwoZGF0YSRzZXNzaW9uJGF2Z19wb3dlcikpe05BfWVsc2V7ZGF0YSRzZXNzaW9uJGF2Z19wb3dlcn0KICAgICBwcmVkaWN0b3JzJHBvd2VyLm5vcm0gPC0gaWYoaXMubnVsbChkYXRhJHNlc3Npb24kbm9ybWFsaXplZF9wb3dlcikpe05BfWVsc2V7ZGF0YSRzZXNzaW9uJG5vcm1hbGl6ZWRfcG93ZXJ9CiAgICAgcHJlZGljdG9ycyR3b3JrIDwtIGlmKGlzLm51bGwoZGF0YSRzZXNzaW9uJHRvdGFsX3dvcmspKXtOQX1lbHNle2RhdGEkc2Vzc2lvbiR0b3RhbF93b3JrfQogICAgIHByZWRpY3RvcnMkc3RyZXNzLnNjb3JlIDwtIGlmKGlzLm51bGwoZGF0YSRzZXNzaW9uJHRyYWluaW5nX3N0cmVzc19zY29yZSkpe05BfQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVsc2Uge2RhdGEkc2Vzc2lvbiR0cmFpbmluZ19zdHJlc3Nfc2NvcmV9CiAgICAgcHJlZGljdG9ycyR0b3RhbC5kaXN0IDwtIGRhdGEkc2Vzc2lvbiR0b3RhbF9kaXN0YW5jZQogICAgIHByZWRpY3RvcnMkaW50ZW5zaXR5LmZhY3RvciA8LSAgaWYoaXMubnVsbChkYXRhJHNlc3Npb24kaW50ZW5zaXR5X2ZhY3Rvcikpe05BfQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZWxzZXtkYXRhJHNlc3Npb24kaW50ZW5zaXR5X2ZhY3Rvcn0KICAgICBhbGwucHJlZGljdG9ycyA8LSByYmluZChhbGwucHJlZGljdG9ycyxwcmVkaWN0b3JzKQogICAgIGFjdGl2aXR5LmlkIDwtIGFjdGl2aXR5LmlkKzEKICAgICAjIHByaW50KGFsbC5wcmVkaWN0b3JzKQogIH0KICAgcmV0dXJuKGFsbC5wcmVkaWN0b3JzKQp9CmBgYAoKIyMjIyBFeGFtcGxlIE91dHB1dAoKVGhlIGZ1bmN0aW9uIGNyZWF0ZWQgYWJvdmUgZ2l2ZXMgdXMgdGhlIGZvbGxvd2luZyByZXN1bHRzIGZvciB0aGUgdGVzdCB3aXRoIFdoaXRlTGlhbSBmaWxlczoKCmBgYHtyfQphdGhsZXRlLnByZWRpY3RvcnMgPC0gY2FsY19wcmVkaWN0b3JzKHdoaXRlZmlsZXMpCmF0aGxldGUucHJlZGljdG9ycwpgYGAKCioqKgoKIyMgU3VwZXJwcmVkaWN0b3JzIGFzIHN1bW1hcnkKClN1cGVycHJlZGljdG9ycyBhcmUgYXZlcmFnZXMgb3IgdG90YWxzIG9mIGVhY2ggb2YgdGhlIGFjdGl2aXR5IHByZWRpY3RvcnMsIGFzIGEgd2F5IHRvIHN1bW1hcml6ZSB0cmFpbmluZyBkYXRhIGZvciBlYWNoIGF0aGxldGUgZHVyaW5nIHRoZSBzZWxlY3RlZCBwZXJpb2QuCkhlcmUgd2UgY2FsY3VsYXRlIGFsbCBvZiB0aGVtIHRvIGNvbXBhcmUgd2l0aCB2bzJtYXggaW4gdGhlIGZpbmFsIHN0ZXAuCgojIyMgRGVmaW5pdGlvbgoKRGVmaW5lIGZ1bmN0aW9uIHRvOgoqIEluaXRpYWxpemUgdmFyaWFibGVzCgpgYGB7cn0KIyBmaW5hbC5kYXRhIDwtIHZvMm1heApwcmVwX3N1cGVycHJlZGljdG9ycyA8LSBmdW5jdGlvbihmaW5hbC5kYXRhKXsKICBmaW5hbC5kYXRhJGFjdGl2aXRpZXMudG90YWwgPC0gTkEKICBmaW5hbC5kYXRhJHRpbWUudG90YWwgPC0gTkEKICBmaW5hbC5kYXRhJHRpbWUuYXZnIDwtIE5BCiAgZmluYWwuZGF0YSRocm1heC5wZXJjLmF2ZyA8LSBOQQogIGZpbmFsLmRhdGEkaHJtYXgubWF4IDwtIE5BCiAgZmluYWwuZGF0YSRocm1heC5pbnRlbnNpdHkudG90YWwgPC0gTkEKICBmaW5hbC5kYXRhJGhybWF4LmludGVuc2l0eS5hdmcgPC0gTkEKICBmaW5hbC5kYXRhJGhyLno1LmF2ZyA8LSBOQQogIGZpbmFsLmRhdGEkaHIuejQuYXZnIDwtIE5BCiAgZmluYWwuZGF0YSRoci56My5hdmcgPC0gTkEKICBmaW5hbC5kYXRhJGhyLnoyLmF2ZyA8LSBOQQogIGZpbmFsLmRhdGEkaHIuejEuYXZnIDwtIE5BCiAgZmluYWwuZGF0YSRoci5hdmcgPC0gTkEKICBmaW5hbC5kYXRhJGNhbC50b3RhbCA8LSBOQQogIGZpbmFsLmRhdGEkY2FsLmF2ZyA8LSBOQQogIGZpbmFsLmRhdGEkcG93ZXIuYXZnIDwtIE5BCiAgZmluYWwuZGF0YSR3b3JrLmF2ZyA8LSBOQQogIGZpbmFsLmRhdGEkc3RyZXNzLnNjb3JlLmF2ZyA8LSBOQQogIGZpbmFsLmRhdGEkZGlzdC50b3RhbCA8LSBOQQogIGZpbmFsLmRhdGEkZGlzdC5hdmcgPC0gTkEKICBmaW5hbC5kYXRhJGludGVuc2l0eS5mYWN0b3IuYXZnIDwtIE5BCiAgcmV0dXJuKGZpbmFsLmRhdGEpCn0KYGBgCgoqIENhbGN1bGF0ZSB2YXJpYWJsZXMKCmBgYHtyfQojIGF0aCA8LSAnTGlhbScKZ2V0X3N1cGVycHJlZGljdG9ycyA8LSBmdW5jdGlvbiAoYXRoLmRhdGEsYXRoLnByZWRpY3RvcnMsYXRoLm5hbWUpIHsKICBhdGguZGF0YVthdGguZGF0YSRuYW1lID09IGF0aC5uYW1lLF0kYWN0aXZpdGllcy50b3RhbCA8LSBucm93KGF0aC5wcmVkaWN0b3JzKQogIGF0aC5kYXRhW2F0aC5kYXRhJG5hbWUgPT0gYXRoLm5hbWUsXSR0aW1lLnRvdGFsIDwtIHN1bShhdGgucHJlZGljdG9ycyR0aW1lLm1pbikKICBhdGguZGF0YVthdGguZGF0YSRuYW1lID09IGF0aC5uYW1lLF0kdGltZS5hdmcgPC0gbWVhbihhdGgucHJlZGljdG9ycyR0aW1lLm1pbixuYS5ybT1UKQogIGF0aC5kYXRhW2F0aC5kYXRhJG5hbWUgPT0gYXRoLm5hbWUsXSRocm1heC5wZXJjLmF2ZyA8LSBtZWFuKGF0aC5wcmVkaWN0b3JzJGhybWF4LnBlcmMsbmEucm09VCkKICBhdGguZGF0YVthdGguZGF0YSRuYW1lID09IGF0aC5uYW1lLF0kaHJtYXgubWF4IDwtIGlmIChzdW0oIWlzLm5hKGF0aC5wcmVkaWN0b3JzJGhybWF4LmFjdGl2aXR5KSkgPT0gMCApe05BfWVsc2V7bWF4KGF0aC5wcmVkaWN0b3JzJGhybWF4LmFjdGl2aXR5LG5hLnJtPVQpfQogIGF0aC5kYXRhW2F0aC5kYXRhJG5hbWUgPT0gYXRoLm5hbWUsXSRocm1heC5pbnRlbnNpdHkudG90YWwgPC0gc3VtKGF0aC5wcmVkaWN0b3JzJGhybWF4LmludGVuc2l0eSxuYS5ybT1UKQogIGF0aC5kYXRhW2F0aC5kYXRhJG5hbWUgPT0gYXRoLm5hbWUsXSRocm1heC5pbnRlbnNpdHkuYXZnIDwtIG1lYW4oYXRoLnByZWRpY3RvcnMkaHJtYXguaW50ZW5zaXR5LG5hLnJtPVQpCiAgYXRoLmRhdGFbYXRoLmRhdGEkbmFtZSA9PSBhdGgubmFtZSxdJGhyLno1LmF2ZyA8LSBtZWFuKGF0aC5wcmVkaWN0b3JzJGhyLno1LG5hLnJtPVQpCiAgYXRoLmRhdGFbYXRoLmRhdGEkbmFtZSA9PSBhdGgubmFtZSxdJGhyLno0LmF2ZyA8LSBtZWFuKGF0aC5wcmVkaWN0b3JzJGhyLno0LG5hLnJtPVQpCiAgYXRoLmRhdGFbYXRoLmRhdGEkbmFtZSA9PSBhdGgubmFtZSxdJGhyLnozLmF2ZyA8LSBtZWFuKGF0aC5wcmVkaWN0b3JzJGhyLnozLG5hLnJtPVQpCiAgYXRoLmRhdGFbYXRoLmRhdGEkbmFtZSA9PSBhdGgubmFtZSxdJGhyLnoyLmF2ZyA8LSBtZWFuKGF0aC5wcmVkaWN0b3JzJGhyLnoyLG5hLnJtPVQpCiAgYXRoLmRhdGFbYXRoLmRhdGEkbmFtZSA9PSBhdGgubmFtZSxdJGhyLnoxLmF2ZyA8LSBtZWFuKGF0aC5wcmVkaWN0b3JzJGhyLnoxLG5hLnJtPVQpCiAgYXRoLmRhdGFbYXRoLmRhdGEkbmFtZSA9PSBhdGgubmFtZSxdJGhyLmF2ZyA8LSBtZWFuKGF0aC5wcmVkaWN0b3JzJGhyLmF2ZyxuYS5ybT1UKQogIGF0aC5kYXRhW2F0aC5kYXRhJG5hbWUgPT0gYXRoLm5hbWUsXSRjYWwudG90YWwgPC0gc3VtKGF0aC5wcmVkaWN0b3JzJGNhbCxuYS5ybT1UKQogIGF0aC5kYXRhW2F0aC5kYXRhJG5hbWUgPT0gYXRoLm5hbWUsXSRjYWwuYXZnIDwtIG1lYW4oYXRoLnByZWRpY3RvcnMkY2FsLG5hLnJtPVQpCiAgYXRoLmRhdGFbYXRoLmRhdGEkbmFtZSA9PSBhdGgubmFtZSxdJHBvd2VyLmF2ZyA8LSBtZWFuKGF0aC5wcmVkaWN0b3JzJHBvd2VyLmF2ZyxuYS5ybT1UKQogIGF0aC5kYXRhW2F0aC5kYXRhJG5hbWUgPT0gYXRoLm5hbWUsXSR3b3JrLmF2ZyA8LSBtZWFuKGF0aC5wcmVkaWN0b3JzJHdvcmssbmEucm09VCkKICBhdGguZGF0YVthdGguZGF0YSRuYW1lID09IGF0aC5uYW1lLF0kc3RyZXNzLnNjb3JlLmF2ZyA8LSBtZWFuKGF0aC5wcmVkaWN0b3JzJHN0cmVzcy5zY29yZSxuYS5ybT1UKQogIGF0aC5kYXRhW2F0aC5kYXRhJG5hbWUgPT0gYXRoLm5hbWUsXSRkaXN0LnRvdGFsIDwtIHN1bShhdGgucHJlZGljdG9ycyR0b3RhbC5kaXN0LG5hLnJtPVQpCiAgYXRoLmRhdGFbYXRoLmRhdGEkbmFtZSA9PSBhdGgubmFtZSxdJGRpc3QuYXZnIDwtIG1lYW4oYXRoLnByZWRpY3RvcnMkdG90YWwuZGlzdCxuYS5ybT1UKQogIGF0aC5kYXRhW2F0aC5kYXRhJG5hbWUgPT0gYXRoLm5hbWUsXSRpbnRlbnNpdHkuZmFjdG9yLmF2ZyA8LSBtZWFuKGF0aC5wcmVkaWN0b3JzJGludGVuc2l0eS5mYWN0b3IsbmEucm09VCkKICBhdGguZGF0YVtpcy5uYShhdGguZGF0YSldIDwtIE5BCiAgcmV0dXJuKGF0aC5kYXRhKQp9CmBgYAoKCiMjIyBDYWxjdWxhdGUgU3VwZXJwcmVkaWN0b3JzCgpDb21iaW5lIHRoZSByZWFkaW5nIG9mIHRoZSBmaWxlcyB3aXRoIHRoZSBzdXBlcnByZWRpY3RvciBjYWxjdWxhdGlvbgoKYGBge3J9CmRpcnMgPC0gbGlzdC5kaXJzKCJTRUxFQ1RFRF9GSUxFUyIscmVjdXJzaXZlPUYpCmF0aC5zdXBlcnByZWRzIDwtIHByZXBfc3VwZXJwcmVkaWN0b3JzKHZvMm1heCkKYGBgCgoKYGBge3J9CmZvciAoYXRoIGluIHZvMm1heCRuYW1lKXsKIyBmb3IgKGF0aCBpbiAnTGlhbScpewogIGF0aC5mb2xkZXIgPC0gcGFzdGUwKGRpcnNbZ3JlcCh0b2xvd2VyKGF0aCksdG9sb3dlcihkaXJzKSldLCIvMDkvIikKICBteWZpbGVzIDwtIHBhc3RlMChhdGguZm9sZGVyLCBsaXN0LmZpbGVzKHBhc3RlMChhdGguZm9sZGVyKSkpCiAgI2dldCBwcmVkaWN0b3JzCiAgYXRoLnByZWRpY3RvcnMgPC0gY2FsY19wcmVkaWN0b3JzKG15ZmlsZXMpCiAgI2dldCBzdXBlcnByZWRpY3RvcnMKICBhdGguc3VwZXJwcmVkcyA8LSBnZXRfc3VwZXJwcmVkaWN0b3JzKGF0aC5zdXBlcnByZWRzLGF0aC5wcmVkaWN0b3JzLGF0aCkKfQpgYGAKCgpBbmQgdGhlIGZpbmFsIHJlc3VsdHMgZm9yIGFsbCB0aGUgYXRobGV0ZXM6CgpgYGB7cn0KYXRoLnN1cGVycHJlZHMKYGBgCgoqKioKCiMjIE1ha2UgY29ycmVsYXRpb24gd2l0aCBWTzJtYXgKCkNvcnJlbGF0ZSBhbGwgc3VwZXJwcmVkaWN0b3JzIHdpdGggVk8ybWF4LgpFYWNoIHBvaW50IGNvcnJlc3BvbmRzIHRvIGFuIGF0aGxldGU6CgpgYGB7cn0KbW9kZWxzIDwtIGFzLmRhdGEuZnJhbWUobWF0cml4KE5BLG5jb2w9MjIpKQptb2RlbHMgPC0gcmJpbmQobW9kZWxzLE5BKQpyb3cubmFtZXMobW9kZWxzKSA8LSBjKCJyc3F1YXJlIiwicHZhbHVlIikKY29sbmFtZXMobW9kZWxzKSA8LSBuYW1lcyhhdGguc3VwZXJwcmVkcylbYyg1LDc6ZGltKGF0aC5zdXBlcnByZWRzKVsyXSldCmBgYAoKCmBgYHtyfQpmb3IgKG1vZCBpbiBuYW1lcyhtb2RlbHMpKXsKICBtZCA8LSBwYXN0ZSgiVk8ybWF4IH4gIixtb2Qsc2VwID0gIiIpCiAgbG1UZW1wID0gbG0obWQsIGRhdGEgPSBhdGguc3VwZXJwcmVkcykgI0NyZWF0ZSB0aGUgbGluZWFyIHJlZ3Jlc3Npb24KICBwbG90KGF0aC5zdXBlcnByZWRzW1ttb2RdXSxhdGguc3VwZXJwcmVkcyRWTzJtYXgsIHBjaCA9IDE2LCBjb2wgPSAiYmx1ZSIseGxhYj1tb2QseWxhYj0iVk8ybWF4IikgI1Bsb3QgdGhlIHJlc3VsdHMKICBhYmxpbmUobG1UZW1wKSAjQWRkIGEgcmVncmVzc2lvbiBsaW5lCiAgIyBwcmludChzdW1tYXJ5KGxtVGVtcCkpCiAgbW9kZWxzW1ttb2RdXSA8LSBjKHN1bW1hcnkobG1UZW1wKSRhZGouci5zcXVhcmVkLHN1bW1hcnkobG1UZW1wKSRjb2VmZmljaWVudHNbMiw0XSkKfQpgYGAKCgpgYGB7cn0KbW9kZWxzCmBgYAoKKiBNb2RlbCB3aXRoIGFsbCB2YXJpYWJsZXMKCmBgYHtyfQptZCA8LSBwYXN0ZSgiVk8ybWF4IH4gIixwYXN0ZShuYW1lcyhtb2RlbHMpWzE6M10sY29sbGFwc2U9IisiKSxzZXAgPSAiIikKbG1UZW1wID0gbG0obWQsIGRhdGEgPSBhdGguc3VwZXJwcmVkcykgI0NyZWF0ZSB0aGUgbGluZWFyIHJlZ3Jlc3Npb24KcGxvdChhdGguc3VwZXJwcmVkc1tbbW9kXV0sYXRoLnN1cGVycHJlZHMkVk8ybWF4LCBwY2ggPSAxNiwgY29sID0gImJsdWUiLHhsYWI9bW9kLHlsYWI9IlZPMm1heCIpICNQbG90IHRoZSByZXN1bHRzCmFibGluZShsbVRlbXApICNBZGQgYSByZWdyZXNzaW9uIGxpbmUKIyBwcmludChzdW1tYXJ5KGxtVGVtcCkpCm1vZGVscyRBTEwgPC0gYyhzdW1tYXJ5KGxtVGVtcCkkYWRqLnIuc3F1YXJlZCxzdW1tYXJ5KGxtVGVtcCkkY29lZmZpY2llbnRzWzIsNF0pCnN1bW1hcnkobG1UZW1wKQptb2RlbHMKYGBgCgoKCgoK